﻿using SharpDepend.Classes.ProfileredDatatypes.Collections.Generic;
using SharpDepend.Datatypes;
using System;
using System.Collections.Generic;

namespace SharpDepend.Classes
{
    public class BuiltInGraphicsHost : IGraphicsDevice
    {
        private int[] mBackBuffer;
        private int mWidth;
        private int mHeight;
        private bool mClipPlaneEnable;
        private Vector4 mClipPlane;
        private PList<Texture> mTextures;
        private System.Collections.ObjectModel.ReadOnlyCollection<Texture> mTexturesReadonly;
        
        /// <summary>
        /// Get the graphics display if available.
        /// </summary>
        public IGraphicsDisplay GraphicsDisplay { get; internal set; }

        public event Action ResolutionChanged;

        public event Action DeviceReady;

        public System.Collections.ObjectModel.ReadOnlyCollection<Texture> Textures
        {
            get { return mTexturesReadonly; }
        }

        /// <summary>
        /// Always ready.
        /// </summary>
        public bool Ready
        {
            get;
            set;
        }

        /// <summary>
        /// Shared lock object to use if this instance is used in difference thread.
        /// </summary>
        public object Locker { get; private set; }

        /// <summary>
        /// Get back buffer array.
        /// </summary>
        public int[] BackBuffer { get { return mBackBuffer; } }

        private static void Swap<T>(ref T lhs, ref T rhs) { T temp; temp = lhs; lhs = rhs; rhs = temp; }

        /// <summary>
        /// Calls wqhen this instance has been created.
        /// </summary>
        /// <param name="graphicsDisplay"></param>
        public void Initialize(IGraphicsDisplay graphicsDisplay)
        {
            GraphicsDisplay = graphicsDisplay;
        }

        public BuiltInGraphicsHost(int width, int height)
        {
            RenderTextures = true;

            Locker = new object();
            mTextures = new PList<Texture>("Textures", this);
            mTexturesReadonly = new System.Collections.ObjectModel.ReadOnlyCollection<Texture>(mTextures);

            SetViewportSize(width, height);

            Ready = true;
        }

        public void SetViewportSize(double width, double height)
        {
            lock (Locker)
            {
                mWidth = (int)width;
                mHeight = (int)height;
                mBackBuffer = new int[mWidth * mHeight];
            }
        }

        public void SetViewportSize(double width, double height, int[] customBackbuffer)
        {
            lock (Locker)
            {
                mWidth = (int)width;
                mHeight = (int)height;
                mBackBuffer = customBackbuffer;

                if (mWidth * mHeight != customBackbuffer.Length)
                {
                    throw new ArgumentOutOfRangeException("Width and height pixels do not match the custom backbuffer.");
                }
            }
        }

        public bool Enable { get; set; }

        public bool RenderTextures { get; set; }

        public int RenderWidth
        {
            get { return mWidth; }
        }

        public int RenderHeight
        {
            get { return mHeight; }
        }

        public void RegisterTexture(Texture texture)
        {
            if (mTextures.Contains(texture))
            {
                throw new Exception("Texture already added.");
            }
            mTextures.Add(texture);
        }

        public void UnregisterTexture(Texture texture)
        {
            if (!mTextures.Contains(texture))
            {
                throw new Exception("Texture can not be removed. It do not exist.");
            }
            mTextures.Remove(texture);
        }

        public void UpdateTexture(Texture texture)
        {
        }

        public void BeginRender()
        {
            if (Enable)
            {
                Array.Clear(mBackBuffer, 0, mBackBuffer.Length);
            }
        }

        public void SetClip(Vector4 plane)
        {
            mClipPlaneEnable = true;
            mClipPlane = plane;
        }

        public void ResetClip()
        {
            mClipPlaneEnable = false;
        }

        public void Draw(ref Vector4 rectangle, Color color)
        {
            if (Enable)
            {
                Vector4 dummy = Vector4.Default;
                Draw(ref rectangle, color, null, ref dummy);
            }
        }

        /// <summary>
        /// Reference: http://www.codeguru.com/cpp/cpp/algorithms/general/article.php/c15989/Tip-An-Optimized-Formula-for-Alpha-Blending-Pixels.htm
        /// </summary>
        public void Draw(ref Vector4 rectangle, Color color, Texture texture, ref Vector4 destinationTextureUV)
        {
            if (Enable)
            {
                int width = mWidth;
                int height = mHeight;
                int[] backBuffer = mBackBuffer;
                bool cEnable = mClipPlaneEnable;
                Vector4 c = mClipPlane;

                int x1 = Math.Max(0, (int)rectangle.X);
                int y1 = Math.Max(0, (int)rectangle.Y);
                int x2 = Math.Max(0, (int)rectangle.X2);
                int y2 = Math.Max(0, (int)rectangle.Y2);
                x1 = Math.Min(width - 1, x1);
                y1 = Math.Min(height - 1, y1);
                x2 = Math.Min(width - 1, x2);
                y2 = Math.Min(height - 1, y2);
                
                int rightClip = (int)Math.Max(0, rectangle.X2 - width);
                int bottomClip = (int)Math.Max(0, rectangle.Y2 - height);

                double alpha2 = 1 / 255.0 * color.A;
                double alpha1 = 1.0 - alpha2;
                byte color_A = color.A;
                byte color_R = color.R;
                byte color_G = color.G;
                byte color_B = color.B;

                if (color.A < 255)
                {
                    if (texture != null && RenderTextures)
                    {
                        double preTrWidth = texture.Width / (double)rectangle.Width;
                        double preTrHeight = texture.Height / (double)rectangle.Height;

                        // Alpha with texture.
                        int sY = (int)(rectangle.Height - (y2 - y1)) - bottomClip;
                        for (int y = y1; y < y2; y++, sY++)
                        {
                            int sX = (int)(rectangle.Width - (x2 - x1)) - rightClip;

                            int tY = (int)(preTrHeight * sY);
                            int tWidthY = texture.Height * tY;
                            int widthY = width * y;

                            for (int x = x1; x < x2; x++, sX++)
                            {
                                if (cEnable)
                                {
                                    if (x < c.X || x > c.X2 || y < c.Y || y > c.Y2)
                                    {
                                        return;
                                    }
                                }

                                // Pick texture color.
                                int tIndex = (int)(tWidthY + preTrWidth * sX);

                                if (tIndex >= texture.Data.Length)
                                {
                                    // TODO: Ugly solution. How to fix this?
                                    continue;
                                }

                                int tColorInt = texture.Data[tIndex];
                                byte nTRed = (byte)(tColorInt & 0xff);
                                byte nTGreen = (byte)((tColorInt & 0xff00) >> 8);
                                byte nTBlue = (byte)((tColorInt & 0xff0000) >> 16); 
                                byte nTAlpha = (byte)((tColorInt & 0xff000000) >> 24);

                                nTRed = (byte)((nTRed * color_R) >> 8);
                                nTGreen = (byte)((nTGreen * color_G) >> 8);
                                nTBlue = (byte)((nTBlue * color_B) >> 8);
                                nTAlpha = (byte)((nTAlpha * color_A) >> 8);

                                int index = widthY + x;

                                int orgColorInt = backBuffer[index];
                                byte nSrcRed = (byte)(tColorInt & 0xff);
                                byte nSrcGreen = (byte)((tColorInt & 0xff00) >> 8);
                                byte nSrcBlue = (byte)((tColorInt & 0xff0000) >> 16);

                                nSrcRed = (byte)((nSrcRed * (byte)~nTAlpha + nTRed * nTAlpha) >> 8);
                                nSrcGreen = (byte)((nSrcGreen * (byte)~nTAlpha + nTGreen * nTAlpha) >> 8);
                                nSrcBlue = (byte)((nSrcBlue * (byte)~nTAlpha + nTBlue * nTAlpha) >> 8);
                                
                                backBuffer[index] = 255 << 24 | nSrcRed << 16 | nSrcGreen << 8 | nSrcBlue;
                            }
                        }
                    }
                    else
                    {
                        // Draw with alpha.
                        for (int y = y1; y < y2; y++)
                        {
                            int widthY = width * y;
                            for (int x = x1; x < x2; x++)
                            {
                                if (cEnable)
                                {
                                    if (x < c.X || x > c.X2 || y < c.Y || y > c.Y2)
                                    {
                                        return;
                                    }
                                }

                                int index = widthY + x;

                                int orgColorInt = backBuffer[index];

                                byte nSrcRed = (byte)(orgColorInt & 0xff);
                                byte nSrcGreen = (byte)((orgColorInt & 0xff00) >> 8);
                                byte nSrcBlue = (byte)((orgColorInt & 0xff0000) >> 16);
                                byte nSrcAlpha = (byte)((orgColorInt & 0xff000000) >> 24);

                                nSrcRed = (byte)((nSrcRed * (byte)~color_A + color_R * color_A) >> 8);
                                nSrcGreen = (byte)((nSrcGreen * (byte)~color_A + color_G * color_A) >> 8);
                                nSrcBlue = (byte)((nSrcBlue * (byte)~color_A + color_B * color_A) >> 8);
                                nSrcAlpha = (byte)(color_A + nSrcAlpha > 255 ? 255 : color_A + nSrcAlpha);
                                
                                backBuffer[index] = nSrcAlpha << 24 | nSrcRed << 16 | nSrcGreen << 8 | nSrcBlue;
                            }
                        }
                    }
                }
                else
                {
                    if (texture != null && RenderTextures)
                    {
                        double rectangleWidth = rectangle.Width;
                        double preTrWidth = texture.Width / (double)rectangle.Width;
                        double preTrHeight = texture.Height / (double)rectangle.Height;
                        var textureWidth = texture.Width;
                        var textureData = texture.Data;
                        var textureDataLength = textureData.Length;

                        // Non-alpha with texture.
                        int sY = (int)(rectangle.Height - (y2 - y1)) - bottomClip;
                        for (int y = y1; y < y2; y++, sY++)
                        {
                            int sX = (int)(rectangleWidth - (x2 - x1)) - rightClip;

                            int tY = (int)(preTrHeight * sY);
                            int tWidthY = textureWidth * tY;
                            int widthY = width * y;

                            for (int x = x1; x < x2; x++, sX++)
                            {
                                if (cEnable)
                                {
                                    if (x < c.X || x > c.X2 || y < c.Y || y > c.Y2)
                                    {
                                        return;
                                    }
                                }

                                // Pick texture color.
                                int tIndex = (int)(tWidthY + preTrWidth * sX);

                                if (tIndex >= textureDataLength)
                                {
                                    // TODO: Ugly solution. How to fix this?
                                    continue;
                                }

                                int tColorInt = textureData[tIndex];
                                byte nTRed = (byte)(tColorInt & 0xff); 
                                byte nTGreen = (byte)((tColorInt & 0xff00) >> 8); 
                                byte nTBlue = (byte)((tColorInt & 0xff0000) >> 16); 
                                byte nTAlpha = (byte)((tColorInt & 0xff000000) >> 24); 

                                if (nTAlpha == 255)
                                {
                                    nTRed = (byte)((color_R * nTRed) >> 8);
                                    nTGreen = (byte)((color_G * nTGreen) >> 8);
                                    nTBlue = (byte)((color_B * nTBlue) >> 8);

                                    int index = widthY + x;
                                    
                                    backBuffer[index] = 255 << 24 | nTRed << 16 | nTGreen << 8 | nTBlue;
                                }
                                else
                                {
                                    int index = widthY + x;

                                    int orgColorInt = backBuffer[index];
                                    byte nSrcRed = (byte)(tColorInt & 0xff);
                                    byte nSrcGreen = (byte)((tColorInt & 0xff00) >> 8);
                                    byte nSrcBlue = (byte)((tColorInt & 0xff0000) >> 16);

                                    nSrcRed = (byte)((nSrcRed * (byte)~nTAlpha + nTRed * nTAlpha) >> 8);
                                    nSrcGreen = (byte)((nSrcGreen * (byte)~nTAlpha + nTGreen * nTAlpha) >> 8);
                                    nSrcBlue = (byte)((nSrcBlue * (byte)~nTAlpha + nTBlue * nTAlpha) >> 8);

                                    backBuffer[index] = 255 << 24 | nTRed << 16 | nTGreen << 8 | nTBlue;
                                }
                            }
                        }
                    }
                    else
                    {
                        // Non-alpha and non-texture.
                        if (cEnable)
                        {
                            x1 = Math.Max(x1, (int)mClipPlane.X1);
                            y1 = Math.Max(y1, (int)mClipPlane.Y1);
                            x2 = Math.Min(x2, (int)mClipPlane.X2);
                            y2 = Math.Min(y2, (int)mClipPlane.Y2);
                            x1 = Math.Max((int)mClipPlane.X1 - 1, x1);
                            y1 = Math.Max((int)mClipPlane.Y1 - 1, y1);
                            x2 = Math.Min((int)mClipPlane.X2 - 1, x2);
                            y2 = Math.Min((int)mClipPlane.Y2 - 1, y2);
                        }

                        for (int y = y1; y < y2; y++)
                        {
                            int start = width * y + x1;
                            int length = x2 - x1;
                            
                            PeformanceHelper.MemSet(backBuffer, start, length, (int)color.Packet);
                        }
                    }
                }
            }
        }

        public void DrawLine(Vector2 start, Vector2 end, Color color)
        {
            if (Enable)
            {
                DrawLineAA((int)start.X, (int)start.Y, (int)end.X, (int)end.Y, color);
            }
        }

        public void DrawLineAA(int x1, int y1, int x2, int y2, Color color)
        {
            DrawLineAA(x1, y1, x2, y2, color.R, color.G, color.B);
        }

        private double decimals(double x)
        {
            return x - (int)x;
        }

        private double reverseDecimals(double x)
        {
            return 1.0 - decimals(x);
        }
        private void dla_plot(uint x, uint y, float br, int r, int g, int b)
        {
            Color color = new Color(255, r, g, b);
            dla_plot(x, y, color, br);
        }
        private void dla_plot(uint x, uint y, Color color, float br)
        {
            if (x < mWidth && x >= 0 &&
                y < mHeight && y >= 0)
            {
                int a = 0;
                int r = 0;
                int g = 0;
                int b = 0;
                GetPixel((int)x, (int)y, ref r, ref g, ref b, ref a);

                //a = (int)(a * (1.0 - br) + color.A * br);
                a = a > color.A ? a : color.A;
                r = (int)(r * (1.0 - br) + color.R * br);
                g = (int)(g * (1.0 - br) + color.G * br);
                b = (int)(b * (1.0 - br) + color.B * br);

                //Color oc = new Color(
                //dla_changeBrightness(color, ref oc, br);
                DrawPixel((int)x, (int)y, r, g, b, a);
            }
        }

        public void DrawLineAA(int x1, int y1, int x2, int y2, int r, int g, int b)
        {
            double dx = (double)x2 - (double)x1;
            double dy = (double)y2 - (double)y1;
            if (Math.Abs(dx) > Math.Abs(dy))
            {
                if (x2 < x1)
                {
                    Swap<int>(ref x1, ref x2);
                    Swap<int>(ref y1, ref y2);
                }
                double gradient = dy / dx;
                double xend = (double)x1;
                double yend = y1 + gradient * (xend - x1);
                double xgap = reverseDecimals(x1 + 0.5);
                uint xpxl1 = (uint)xend;
                uint ypxl1 = (uint)yend;
                dla_plot(xpxl1, ypxl1, (float)(reverseDecimals(yend) * xgap), r, g, b);
                dla_plot(xpxl1, ypxl1 + 1, (float)(decimals(yend) * xgap), r, g, b);
                double intery = yend + gradient;

                xend = x2;
                yend = y2 + gradient * (xend - x2);
                xgap = decimals(x2 + 0.5);
                uint xpxl2 = (uint)xend;
                uint ypxl2 = (uint)yend;
                dla_plot(xpxl2, ypxl2, (float)(reverseDecimals(yend) * xgap), r, g, b);
                dla_plot(xpxl2, ypxl2 + 1, (float)(decimals(yend) * xgap), r, g, b);

                uint x;
                for (x = xpxl1 + 1; x <= (xpxl2 - 1) && x < mWidth; x++)
                {
                    dla_plot(x, (uint)intery, (float)reverseDecimals(intery), r, g, b);
                    dla_plot(x, (uint)intery + 1, (float)decimals(intery), r, g, b);
                    intery += gradient;
                }
            }
            else
            {
                if (y2 < y1)
                {
                    Swap<int>(ref x1, ref x2);
                    Swap<int>(ref y1, ref y2);
                }
                double gradient = dx / dy;
                double yend = y1;
                double xend = x1 + gradient * (yend - y1);
                double ygap = reverseDecimals(y1 + 0.5);
                uint ypxl1 = (uint)yend;
                uint xpxl1 = (uint)xend;
                dla_plot(xpxl1, ypxl1, (float)(reverseDecimals(xend) * ygap), r, g, b);
                dla_plot(xpxl1, ypxl1 + 1, (float)(decimals(xend) * ygap), r, g, b);
                double interx = xend + gradient;

                yend = y2;
                xend = x2 + gradient * (yend - y2);
                ygap = decimals(y2 + 0.5);
                uint ypxl2 = (uint)yend;
                uint xpxl2 = (uint)(xend);
                dla_plot(xpxl2, ypxl2, (float)(reverseDecimals(xend) * ygap), r, g, b);
                dla_plot(xpxl2, ypxl2 + 1, (float)(decimals(xend) * ygap), r, g, b);

                uint y;
                for (y = ypxl1 + 1; y <= (ypxl2 - 1) && y < mHeight; y++)
                {
                    dla_plot((uint)(interx), y, (float)reverseDecimals(interx), r, g, b);
                    dla_plot((uint)(interx) + 1, y, (float)decimals(interx), r, g, b);
                    interx += gradient;
                }
            }
        }

        public void DrawWuLine(int x1, int y1, int x2, int y2, Color color)
        {
            //int colorINT = 0;
            //RMath.Convert.ARGBtoINT(color.A, color.R, color.G, color.B, ref colorINT);
            DrawWuLine((short)x1, (short)y1, (short)x2, (short)y2, (int)color.PacketSigned);
        }

        public void DrawWuLine(double x1, double y1, double x2, double y2)
        {
            DrawWuLine((short)x1, (short)y1, (short)x2, (short)y2, -1);
        }

        /// <summary>
        ///  Source: http://www.codeproject.com/Articles/13360/Antialiasing-Wu-Algorithm
        /// </summary>
        public void DrawWuLine(short x1, short y1, short x2, short y2, int clrLine)
        {
            if ((x1 < 0 && x2 < 0) || (y1 < 0 && y2 < 0) || (x1 > mWidth && x2 > mWidth) || (y1 > mHeight && y2 > mHeight)) return;

            int X0 = x1, Y0 = y1, X1 = x2, Y1 = y2;

            /* Make sure the line runs top to bottom */
            if (Y0 > Y1)
            {
                int Temp = Y0;
                Y0 = Y1;
                Y1 = Temp;
                Temp = X0; X0 = X1; X1 = Temp;
            }

            /* Draw the initial pixel, which is always exactly intersected by
            the line and so needs no weighting */
            DrawPixel(X0, Y0, clrLine);

            int XDir, DeltaX = X1 - X0;
            if (DeltaX >= 0)
            {
                XDir = 1;
            }
            else
            {
                XDir = -1;
                DeltaX = 0 - DeltaX; /* make DeltaX positive */
            }

            /* Special-case horizontal, vertical, and diagonal lines, which
            require no weighting because they go right through the center of
            every pixel */
            int DeltaY = Y1 - Y0;
            if (DeltaY == 0)
            {
                /* Horizontal line */
                while (DeltaX-- != 0)
                {
                    X0 += XDir;
                    DrawPixel(X0, Y0, clrLine);
                }
                return;
            }
            if (DeltaX == 0)
            {
                /* Vertical line */
                do
                {
                    ++Y0;
                    DrawPixel(X0, Y0, clrLine);
                } while (DeltaY-- > 0);
                return;
            }

            if (DeltaX == DeltaY)
            {
                /* Diagonal line */
                do
                {
                    X0 += XDir;
                    ++Y0;
                    DrawPixel(X0, Y0, clrLine);
                } while (DeltaY-- > 0);
                return;
            }

            ulong ErrorAdj;
            ulong ErrorAccTemp, Weighting;

            /* Line is not horizontal, diagonal, or vertical */
            ulong ErrorAcc = 0;  /* initialize the line error accumulator to 0 */

            int al = (int)(clrLine >> 24);
            int rl = (int)(clrLine >> 16);
            int gl = (int)(clrLine >> 8);
            int bl = (int)(clrLine >> 0);
            double grayl = rl * 0.299 + gl * 0.587 + bl * 0.114;

            /* Is this an X-major or Y-major line? */
            if (DeltaY > DeltaX)
            {
                /* Y-major line; calculate 16-bit fixed-point fractional part of a
	            pixel that X advances each time Y advances 1 pixel, truncating the
                result so that we won't overrun the endpoint along the X axis */
                ErrorAdj = ((ulong)DeltaX << 16) / (ulong)DeltaY;
                /* Draw all pixels other than the first and last */
                while (--DeltaY != 0)
                {
                    ErrorAccTemp = ErrorAcc;   /* remember currrent accumulated error */
                    ErrorAcc += ErrorAdj;      /* calculate error for next pixel */
                    if (ErrorAcc <= ErrorAccTemp)
                    {
                        /* The error accumulator turned over, so advance the X coord */
                        X0 += XDir;
                    }
                    ++Y0; /* Y-major, so always advance Y */
                          /* The IntensityBits most significant bits of ErrorAcc give us the
                          intensity weighting for this pixel, and the complement of the
                  weighting for the paired pixel */
                    Weighting = ErrorAcc >> 8;
                    //ASSERT( Weighting < 256 );
                    //ASSERT( ( Weighting ^ 255 ) < 256 );

                    //int clrBackGround = getPixel( X0, Y0 );
                    int ri = 0;
                    int gi = 0;
                    int bi = 0;
                    int ai = 0;
                    GetPixel(X0, Y0, ref ri, ref gi, ref bi, ref ai);
                    int bb = bi;
                    int gb = gi;
                    int rb = ri;
                    int ab = ai;
                    double grayb = rb * 0.299 + gb * 0.587 + bb * 0.114;

                    byte rr = (rb > rl ? ((byte)(((double)(grayl < grayb ? Weighting : (Weighting ^ 255))) / 255.0 * (rb - rl) + rl)) : ((byte)(((double)(grayl < grayb ? Weighting : (Weighting ^ 255))) / 255.0 * (rl - rb) + rb)));
                    byte gr = (gb > gl ? ((byte)(((double)(grayl < grayb ? Weighting : (Weighting ^ 255))) / 255.0 * (gb - gl) + gl)) : ((byte)(((double)(grayl < grayb ? Weighting : (Weighting ^ 255))) / 255.0 * (gl - gb) + gb)));
                    byte br = (bb > bl ? ((byte)(((double)(grayl < grayb ? Weighting : (Weighting ^ 255))) / 255.0 * (bb - bl) + bl)) : ((byte)(((double)(grayl < grayb ? Weighting : (Weighting ^ 255))) / 255.0 * (bl - bb) + bb)));
                    byte ar = (ab > al ? ((byte)(((double)(grayl < grayb ? Weighting : (Weighting ^ 255))) / 255.0 * (ab - al) + al)) : ((byte)(((double)(grayl < grayb ? Weighting : (Weighting ^ 255))) / 255.0 * (al - ab) + ab)));
                    DrawPixel(X0, Y0, rr, gr, br, ar);

                    GetPixel(X0 + XDir, Y0, ref ri, ref gi, ref bi, ref ai);
                    rb = ri;
                    gb = gi;
                    bb = bi;
                    ab = ai;
                    grayb = rb * 0.299 + gb * 0.587 + bb * 0.114;

                    rr = (rb > rl ? ((byte)(((double)(grayl < grayb ? (Weighting ^ 255) : Weighting)) / 255.0 * (rb - rl) + rl)) : ((byte)(((double)(grayl < grayb ? (Weighting ^ 255) : Weighting)) / 255.0 * (rl - rb) + rb)));
                    gr = (gb > gl ? ((byte)(((double)(grayl < grayb ? (Weighting ^ 255) : Weighting)) / 255.0 * (gb - gl) + gl)) : ((byte)(((double)(grayl < grayb ? (Weighting ^ 255) : Weighting)) / 255.0 * (gl - gb) + gb)));
                    br = (bb > bl ? ((byte)(((double)(grayl < grayb ? (Weighting ^ 255) : Weighting)) / 255.0 * (bb - bl) + bl)) : ((byte)(((double)(grayl < grayb ? (Weighting ^ 255) : Weighting)) / 255.0 * (bl - bb) + bb)));
                    ar = (ab > al ? ((byte)(((double)(grayl < grayb ? (Weighting ^ 255) : Weighting)) / 255.0 * (ab - al) + al)) : ((byte)(((double)(grayl < grayb ? (Weighting ^ 255) : Weighting)) / 255.0 * (al - ab) + ab)));
                    DrawPixel(X0 + XDir, Y0, rr, gr, br, ar);
                }
                /* Draw the final pixel, which is always exactly intersected by the line
                and so needs no weighting */
                DrawPixel(X1, Y1, clrLine);
                return;
            }
            /* It's an X-major line; calculate 16-bit fixed-point fractional part of a
            pixel that Y advances each time X advances 1 pixel, truncating the
            result to avoid overrunning the endpoint along the X axis */
            ErrorAdj = ((ulong)DeltaY << 16) / (ulong)DeltaX;
            /* Draw all pixels other than the first and last */
            while (--DeltaX > 0)
            {
                ErrorAccTemp = ErrorAcc;   /* remember currrent accumulated error */
                ErrorAcc += ErrorAdj;      /* calculate error for next pixel */
                if (ErrorAcc <= ErrorAccTemp)
                {
                    /* The error accumulator turned over, so advance the Y coord */
                    ++Y0;
                }
                X0 += XDir; /* X-major, so always advance X */
                /* The IntensityBits most significant bits of ErrorAcc give us the
                intensity weighting for this pixel, and the complement of the
	            weighting for the paired pixel */
                Weighting = ErrorAcc >> 8;
                //ASSERT( Weighting < 256 );
                //ASSERT( ( Weighting ^ 255 ) < 256 );

                int ri = 0;
                int gi = 0;
                int bi = 0;
                int ai = 0;
                GetPixel(X0, Y0, ref ri, ref gi, ref bi, ref ai);
                int bb = bi;
                int gb = gi;
                int rb = ri;
                int ab = ai;
                double grayb = rb * 0.299 + gb * 0.587 + bb * 0.114;

                byte rr = (rb > rl ? ((byte)(((double)(grayl < grayb ? Weighting : (Weighting ^ 255))) / 255.0 * (rb - rl) + rl)) : ((byte)(((double)(grayl < grayb ? Weighting : (Weighting ^ 255))) / 255.0 * (rl - rb) + rb)));
                byte gr = (gb > gl ? ((byte)(((double)(grayl < grayb ? Weighting : (Weighting ^ 255))) / 255.0 * (gb - gl) + gl)) : ((byte)(((double)(grayl < grayb ? Weighting : (Weighting ^ 255))) / 255.0 * (gl - gb) + gb)));
                byte br = (bb > bl ? ((byte)(((double)(grayl < grayb ? Weighting : (Weighting ^ 255))) / 255.0 * (bb - bl) + bl)) : ((byte)(((double)(grayl < grayb ? Weighting : (Weighting ^ 255))) / 255.0 * (bl - bb) + bb)));
                byte ar = (ab > al ? ((byte)(((double)(grayl < grayb ? Weighting : (Weighting ^ 255))) / 255.0 * (ab - al) + al)) : ((byte)(((double)(grayl < grayb ? Weighting : (Weighting ^ 255))) / 255.0 * (al - ab) + ab)));
                DrawPixel(X0, Y0, rr, gr, br, ar);

                GetPixel(X0, Y0 + 1, ref ri, ref gi, ref bi, ref ai);

                rb = ri;
                gb = gi;
                bb = bi;
                ab = ai;
                grayb = rb * 0.299 + gb * 0.587 + bb * 0.114;

                rr = (rb > rl ? ((byte)(((double)(grayl < grayb ? (Weighting ^ 255) : Weighting)) / 255.0 * (rb - rl) + rl)) : ((byte)(((double)(grayl < grayb ? (Weighting ^ 255) : Weighting)) / 255.0 * (rl - rb) + rb)));
                gr = (gb > gl ? ((byte)(((double)(grayl < grayb ? (Weighting ^ 255) : Weighting)) / 255.0 * (gb - gl) + gl)) : ((byte)(((double)(grayl < grayb ? (Weighting ^ 255) : Weighting)) / 255.0 * (gl - gb) + gb)));
                br = (bb > bl ? ((byte)(((double)(grayl < grayb ? (Weighting ^ 255) : Weighting)) / 255.0 * (bb - bl) + bl)) : ((byte)(((double)(grayl < grayb ? (Weighting ^ 255) : Weighting)) / 255.0 * (bl - bb) + bb)));
                ar = (ab > al ? ((byte)(((double)(grayl < grayb ? (Weighting ^ 255) : Weighting)) / 255.0 * (ab - al) + al)) : ((byte)(((double)(grayl < grayb ? (Weighting ^ 255) : Weighting)) / 255.0 * (al - ab) + ab)));
                DrawPixel(X0, Y0 + 1, rr, gr, br, ar);
            }

            /* Draw the final pixel, which is always exactly intersected by the line and so needs no weighting */
            DrawPixel(X1, Y1, clrLine);
        }

        public void GetPixel(int x, int y, ref int r, ref int g, ref int b, ref int a)
        {
            int index = mWidth * y + x;

            int color = mBackBuffer[index];

            Color c = new Color(color);

            r = c.R;
            g = c.G;
            b = c.B;
            a = c.A;
        }


        private void GetPixel(int x, int y, ref Color color)
        {
            int index = y * mWidth + x;
            if (index >= 0 && index < mBackBuffer.Length)
            {
                color.PacketSigned = mBackBuffer[index];
            }
        }

        public void DrawPixel(int x, int y, int r, int g, int b, int a)
        {
            Color c = new Color(a, r, g, b);
            
            DrawPixel(x, y, c.PacketSigned);
        }

        public void DrawPixel(int x, int y, int color)
        {
            DrawPixel(x, y, new Color(color));
        }

        private void DrawPixel(int x, int y, Color color)
        {
            Vector4 c = mClipPlane;
            bool cEnable = mClipPlaneEnable;

            int rX = x;
            int rY = y;
            if (rX < mWidth && rX > 0 &&
                rY < mHeight && rY > 0)
            {
                if (cEnable)
                {
                    if (x < c.X || x > c.X2 || y < c.Y || y > c.Y2)
                    {
                        return;
                    }
                }
                int index = mWidth * rY + rX;
                mBackBuffer[index] = (int)color.Packet;
            }
        }

        public void DrawCircle(Vector2 position, double radius, Color color)
        {
            if (Enable)
            {
                int width = mWidth;
                int height = mHeight;
                int[] backBuffer = mBackBuffer;
                bool cEnable = mClipPlaneEnable;
                Vector4 c = mClipPlane;

                int x0i = (int)position.X;
                int y0i = (int)position.Y;
                int rad = (int)radius;

                for (int y = -rad; y <= rad; y++)
                {
                    for (int x = -rad; x <= rad; x++)
                    {
                        if (x * x + y * y <= rad * rad)
                        {
                            int rX = (int)(x0i + x);
                            int rY = (int)(y0i + y);
                            if (rX < width && rX > 0 &&
                                rY < height && rY > 0)
                            {
                                if (cEnable)
                                {
                                    if (x < c.X || x > c.X2 || y < c.Y || y > c.Y2)
                                    {
                                        return;
                                    }
                                }
                                int index = width * rY + rX;
                                backBuffer[index] = (int)color.Packet;
                            }
                        }
                    }
                }
            }
        }

        public void EndRender()
        {

        }

        public bool ManualRender
        {
            get { return false; }
        }

        public bool ManualRendering
        {
            get
            {
                return false;
            }
        }

        public void DisposeDevice()
        {
            Ready = false;
        }

        /// <summary>
        /// Not used in BuiltInGraphicsHost.
        /// </summary>
        public event Action RaiseOnRender;
        public event Action OnManualRender;

        public void SetGraphicsDevice(GraphicDevice device)
        {
            throw new NotImplementedException();
        }

        public void DrawTriangles(FastStructList<Vector2> vertices, FastStructList<ushort> indices, FastStructList<Color> colors)
        {
            throw new NotImplementedException();
        }
    }
}
